#include <stdio.h>
#include "int_expr.h"
#include <string.h>
#include <assert.h>
#ifdef WIN32
#include "def_for_win.h"
#endif //WIN32
#include "logo_lib_p.h"

static int plus_operand (value_t* v1, value_t* v2, value_t* result);
static char* cat2strings (char* s1, char* s2);

int calc_expr (rte_t* rte, tree_node_t* expr, value_t* result)
{
   assert (expr);
   assert (expr->node_type == ExpNode);
   assert (rte);
   assert (result);
   assert (rte->rtn_lst);

   cur_lineno(rte) = expr->line_no;
   switch (expr->type.exp) {
   case ConstExp:
      result->type = NumericVal;
      result->val = expr->attr.val;
      return 0;

   case OpExp:
      return op_expr (rte, expr, result);

   case IdExp:
      return id_expr (rte, expr, result);

   case StrExp:
      return string_expr (expr, result);

   case RtnExp:
      return routine_expr (rte, expr, result);

   default:
      assert (0);
      break;
   }
   return 0;
}

int id_expr (rte_t* rte, tree_node_t* expr, value_t* result)
{
   assert (expr);
   assert (expr->node_type == ExpNode);
   assert (rte);
   assert (result);
   assert (expr->attr.id_str);
   assert (expr->type.exp == IdExp);

   variable_t* var;
   var = (variable_t*) find_in_list ((list_node_t*)(cur_var_list(rte)), expr->attr.id_str);
   if (!var) {
      print_info (ERROR_INFO, "Vaiable %s not initialize at line %d.",
               expr->attr.id_str, expr->line_no);
      return -1;
   }
   result->copy_value(&var->value);
   return 0;
}

int string_expr (tree_node_t* expr, value_t* result)
{
   char* str;

   assert (expr);
   assert (expr->node_type == ExpNode);
   assert (result);
   assert (expr->attr.str_val);
   assert (expr->type.exp == StrExp);

   result->type = StringVal;
   str = (char*) malloc (strlen(expr->attr.str_val) +1 );
   CHECK_MEM(str);
   /* the string has leading " and trailing ", remove them*/
   strcpy (str, (expr->attr.str_val)+1);
   assert (strlen(str) > 0);
   str[strlen(str)-1] = '\0';
   result->str = str;
   return 0;
}

#define CHECK_STRING_OPERATION(v,line_no) \
         do {\
            if (v.type == StringVal) { \
               print_info (ERROR_INFO, \
                     "No operation defined for strings for operand -, *, /. At line: %d.", \
                     line_no); \
               return -1; \
            }\
         } while (0)

#define CHECK_SAME_TYPE(v1,v2,line_no) \
         do { \
            int ret = compare_value_type (v1, v2, line_no);\
            if (ret != 0) {\
               return ret;\
            }\
         } while (0)

#define SET_BOOL(result,cond) \
         do { \
            result->type = NumericVal; \
            if ((cond)) {     \
               result->val = 1;     \
            } else {    \
               result->val = 0;     \
            }     \
         } while (0)

int op_expr (rte_t* rte, tree_node_t* expr, value_t* result)
{
   value_t v[2];
   int ret, i;

   assert (expr);
   assert (expr->node_type == ExpNode);
   assert (rte);
   assert (rte->rtn_lst);
   assert (result);
   assert (expr->type.exp == OpExp);

   /* TOKEN_NOT is the only uni-oprand*/
   if (expr->attr.op == TOKEN_NOT) {
      ret = calc_expr (rte, expr->child[0], &v[0]);
      if (ret != 0) {
         return ret;
      }
      CHECK_STRING_OPERATION (v[0], expr->line_no);
      SET_BOOL (result, (v[0].val == 0 ));
      return 0;
   }
   /* all other tokens operate on v[0] and v[1]*/
   for (i = 0; i < 2; i++) {
      ret = calc_expr (rte, expr->child[i], &v[i]);
      if (ret != 0) {
         return ret;
      }
   }

   assert (v[0].type != NoneVal && v[1].type != NoneVal);
   switch (expr->attr.op) {
   case TOKEN_AND:
      CHECK_SAME_TYPE(&v[0], &v[1], expr->line_no);
      CHECK_STRING_OPERATION (v[0], expr->line_no);
      SET_BOOL (result, (v[0].val != 0 && v[1].val != 0));
      return 0;

   case TOKEN_OR:
      CHECK_SAME_TYPE(&v[0], &v[1], expr->line_no);
      CHECK_STRING_OPERATION (v[0], expr->line_no);
      SET_BOOL (result, (v[0].val != 0 || v[1].val != 0));
      return 0;

   case TOKEN_EQUAL:
      CHECK_SAME_TYPE(&v[0], &v[1], expr->line_no);
      ret = compare_value (&v[0], &v[1]);
      SET_BOOL (result, (ret == 0));
      return 0;

   case TOKEN_LE:
      CHECK_SAME_TYPE(&v[0], &v[1], expr->line_no);
      ret = compare_value (&v[0], &v[1]);
      SET_BOOL (result, (ret == 0 || ret < 0));
      return 0;

   case TOKEN_GE:
      CHECK_SAME_TYPE(&v[0], &v[1], expr->line_no);
      ret = compare_value (&v[0], &v[1]);
      SET_BOOL (result, (ret == 0 || ret > 0));
      return 0;

   case TOKEN_NOT_EQUAL:
      CHECK_SAME_TYPE(&v[0], &v[1], expr->line_no);
      ret = compare_value (&v[0], &v[1]);
      SET_BOOL (result, (ret != 0));
      return 0;

   case TOKEN_LT:
      CHECK_SAME_TYPE(&v[0], &v[1], expr->line_no);
      ret = compare_value (&v[0], &v[1]);
      SET_BOOL (result, (ret < 0));
      return 0;

   case TOKEN_GT:
      CHECK_SAME_TYPE(&v[0], &v[1], expr->line_no);
      ret = compare_value (&v[0], &v[1]);
      SET_BOOL (result, (ret > 0));
      return 0;

   case TOKEN_PLUS:
      return plus_operand(&v[0], &v[1], result);

   case TOKEN_MINUS:
      CHECK_SAME_TYPE(&v[0], &v[1], expr->line_no);
      CHECK_STRING_OPERATION (v[0], expr->line_no);
      result->type = NumericVal;
      result->val = v[0].val - v[1].val;
      return 0;

   case TOKEN_MUL:
      CHECK_SAME_TYPE(&v[0], &v[1], expr->line_no);
      CHECK_STRING_OPERATION (v[0], expr->line_no);
      result->type = NumericVal;
      result->val = v[0].val * v[1].val;
      return 0;

   case TOKEN_DIV:
      CHECK_SAME_TYPE(&v[0], &v[1], expr->line_no);
      CHECK_STRING_OPERATION (v[0], expr->line_no);
      if (v[1].val == 0) {
         print_info (ERROR_INFO, "Divide by 0. At line %d.", expr->line_no);
         return -1;
      }
      result->type = NumericVal;
      result->val = v[0].val / v[1].val;
      return 0;

    default:
      assert (0);
      return -1;
   }
}

int compare_value_type (value_t* v1, value_t* v2, int line_no)
{
   if (v1->type != v2->type) {
      print_info (ERROR_INFO, "Numeric value cannot be compared/calculated with string. At line %d.",
               line_no);
      return -1;
   }
   return 0;
}

int compare_value (value_t* v1, value_t* v2)
{
   assert (v1 && v2);
   assert (v1->type == v2->type);

   /* value type must be the same. */
   if (v1->type  == StringVal) {
      return strcmp (v1->str, v2->str); 
   } else {
      if (v1->val > v2->val) {
         return 1;
      } else if (v1->val == v2->val) {
         return 0;
      } else {
         return -1;
      } 
   }
}

int routine_expr (rte_t* rte, tree_node_t* expr, value_t* result)
{
   routine_t* callee;
   int caller_paras, callee_paras;
   value_t* para_values = NULL;
   int i, ret;
   tree_node_t* para = NULL;

   assert (expr);
   assert (rte);
   assert (rte->rtn_lst);
   assert (result);
   assert ((expr->node_type == ExpNode && expr->type.exp == RtnExp) ||
          (expr->node_type == StmtNode && expr->type.stmt == ROUTINE_STMT));

   /* Check whether the routine exists*/
   callee = (routine_t*) find_in_list ((list_node_t*)(rte->rtn_lst), expr->attr.id_str);
   if (!callee ) {
      print_info (ERROR_INFO, "Routine %s does not exist. At line: %d.", expr->attr.id_str, expr->line_no);
      return -1;
   }

   /* Compare parameter numbers of caller and callee.*/
   caller_paras = count_paras (expr);
   if (callee->func) {
      /* a predefined routine. The parameter number will be checked inside
       *  the routine.
       */
      callee_paras = caller_paras;
   } else {
      callee_paras = count_paras (callee->tree);
   }
   if (caller_paras != callee_paras) {
      print_info (ERROR_INFO, "The routine called needs %d parameters and the caller provides %d parameters. At line %d.",
               callee_paras, caller_paras, expr->line_no);
      return -1;
   }

   assert (caller_paras == callee_paras);
   if (caller_paras != 0) {
      para_values = new value_t[caller_paras];
      CHECK_MEM (para_values);
   }

   para = expr->child[0];
   i = 0;
   while (para) {
      assert (i < caller_paras);
      ret = calc_expr (rte, para, para_values + i);
      if (ret != 0) {
         if (para_values) {
            delete [] para_values;
         }
         return -1;
      }
      i++;
      para = para->sibling;
   }

   result->type = NoneVal;

   ret = exec_routine (rte, callee, para_values, caller_paras, result);
   
   if (para_values) {
      delete [] para_values;
   }

   if (ret >= 0 && result->type == NoneVal && expr->node_type != StmtNode ) {
      print_info (ERROR_INFO, "Routine %s doesn't return value. At line %d.", 
               expr->attr.id_str, expr->line_no);
      return -1;
   }
   ret = (ret > 0 ? 0 : ret);
   return ret;
}

int count_paras (tree_node_t* t)
{
   int n = 0;
   tree_node_t* cur;

   assert (t);
   assert ((t->node_type == ExpNode && t->type.exp == RtnExp) ||
           (t->node_type == StmtNode && (t->type.stmt == TOKEN_LEARN || t->type.stmt == ROUTINE_STMT) ));

   cur = t->child[0];
   while (cur) {
      n++;
      cur = cur->sibling;
   }
   return n;
}

variable_t* create_variable (rte_t* rte, tree_node_t* id_expr)
{
   variable_t* var;

   assert (id_expr->node_type == ExpNode);
   assert (id_expr->type.exp == IdExp);
   assert (rte);

   var = (variable_t*) malloc (sizeof (variable_t));
   CHECK_MEM (var);
   memset (var, 0, sizeof (variable_t));

   var->node_id = (char*) malloc (strlen (id_expr->attr.id_str) + 1);
   CHECK_MEM (var->node_id);
   strcpy (var->node_id, id_expr->attr.id_str);

   var->next_node = (list_node_t*) (cur_var_list(rte));
   cur_var_list(rte) = var;

   return var;
}

#define MAX_NUMBER_STR 256

static int plus_operand (value_t* v1, value_t* v2, value_t* result)
{
   char nbr_str[MAX_NUMBER_STR];
   assert (v1->type != NoneVal && v2->type != NoneVal);

   if (v1->type == v2->type && v1->type == NumericVal) {
      result->type = NumericVal;
      result->val = v1->val + v2->val;
      return 0;
   }
   if (v1->type == v2->type && v1->type == StringVal) {
      result->type = StringVal;
      result->str = cat2strings(v1->str, v2->str);
      return 0;
   }
   assert (v1->type != v2->type);
   result->type = StringVal;   
   if (v1->type == NumericVal) {
      snprintf (nbr_str, MAX_NUMBER_STR, "%g", v1->val);
      result->type = StringVal;
      result->str = cat2strings(nbr_str, v2->str);
      return 0;
   } 
   snprintf (nbr_str, MAX_NUMBER_STR, "%g", v2->val);
   result->type = StringVal;
   result->str = cat2strings(v1->str, nbr_str);
   return 0;
}

static char* cat2strings (char* s1, char* s2)
{
   char* str;
   str = (char*) malloc (strlen(s1)+strlen(s2)+1);
   CHECK_MEM(str);
   strcpy(str, s1);
   strcat(str, s2);
   return str;
}
